#!/usr/bin/python

"""Parses a BGG auction geeklist to keep track of bids.

This is a testbed for code for parsing general BGG auction list XML that I'll
use for generating a tracking app for BGG geeklists. """

__author__ = "Bruce Murphy"

import datetime
import dateutil.parser
import dateutil.tz
import re
import sys
import xml.etree.ElementTree as ET


def ReadAndParseXML(xml_filename):
  """Wrapper around our XML reading library call."""
  tree = ET.parse(xml_filename)
  return tree.getroot()


def BasicXMLReport(xmlroot):
  """Prints out a simple indented XML structure diagram"""

  def PrintElem(element, indent):
    "Prints a single element with indentation, recurses over children."
    if "objectname" in element.attrib:
      name = ":  " + element.attrib["objectname"]
    else:
      name = ""
    print indent + element.tag + name
    for child in element:
      PrintElem(child, indent + "  ")

  PrintElem(xmlroot, "")


def HasCommentsBy(item, bgguser):
  "Comments on a geeklist item from bgguser exist."
  bgguser = bgguser.lower()
  for child in item:
    if not child.tag == "comment":
      continue

    if child.attrib["username"].lower() == bgguser:
      return True

  return False


def ParseXMLDate(datestring):
  "Parses an XML date and returns a datetime object"
  return dateutil.parser.parse(datestring)


def GetBids(item, ignoreusers=None):
  "Returns bid values and usernames by parsing the comments."
  # TODO(packrat). This should also notify of following comments in case of
  # parse errors.
  bids = [{"username":"", "bid":0}]
  sold = []
  if ignoreusers is None:
    ignoreusers = []

  for child in item:
    if child.tag != "comment":
      continue
    if child.attrib["username"] in ignoreusers:
      continue

    sold_value = CommentSoldText(child.text)
    if sold_value:
      sold.append(sold_value)
    else:

      # find and break out the bids.
      m = re.search(r"\$([0-9]+)", child.text)
      if m:
        bid = int(m.group(1))
        if bid > bids[-1]["bid"]:
          bids.append({"bid":bid,
                       "username":child.attrib["username"]})

  return bids[1:], sold


def GetCommentDate(comment):
  "Returns the date of the comment"
  return ParseXMLDate(comment.attrib["date"])


def GetLatestCommentDate(item):
  "Returns a datetime of the latest comment date on an item."
  latest_date = None
  for child in item:
    if child.tag != "comment":
      continue

    comment_date = GetCommentDate(child)
    if latest_date is None or latest_date < comment_date:
      latest_date = comment_date

  return latest_date


def GetItemBody(item):
  "Returns the body of an item."
  for child in item:
    if child.tag == "body":
      return child.text

  return ""


def BodySoldTest(bodytext):
  "Simple test for item finished applicable to the body of a geeklist item"
  if bodytext.find("SOLD") >= 0:
    return True

  return False


def CommentSoldText(text):
  """Simple test to see if a comment contains a sold statement.

  Returns false if there isn't a final bid here."""

  if text.find("YES") >= 0:
    m = re.search(r"YES\. +([^$]+) +(\$[0-9.]+)", text)
    if m:
      return {"user": m.group(1), "amount": m.group(2)}
    else:
      return {"error": "Unable to parse final value bid."}

  return False


def AuctionReport(xmlroot, bgguser):
  itemno = 0
  now = datetime.datetime.now(dateutil.tz.tzlocal())

  purchase_report = {}

  for child in xmlroot:
    if child.tag != "item":
      continue

    itemno += 1
    if not HasCommentsBy(child, bgguser):
      continue

    item_name = child.attrib["objectname"].encode("utf-8")
    print "%d.  " % itemno + item_name,

    sold_val = BodySoldTest(GetItemBody(child))
    if sold_val:
      print " (SOLD)"
    else:
      print

    #bids = GetBids(child, ignoreusers=["PBrennan"])
    (bids, sold) = GetBids(child, ignoreusers=["xxthepackrat"])
    for bid in bids:
      print bid

    latest_comment_date = GetLatestCommentDate(child)
    if latest_comment_date:
      delta = now - latest_comment_date
      if delta.days > 2:
        age_string = "%d days ago" % delta.days
      elif delta.days == 1:
        age_string = "1 day ago"
      else:
        age_string = "less than 1 day ago"

      print "Last comment: " + age_string

    if sold:
      if "error" in sold[0]:
        print "++ SOLD, error", sold[0]["error"]
      else:
        user = sold[0]["user"]
        amount = sold[0]["amount"]
        print "++ SOLD to %s for %s" % (user, amount)

        if not user in purchase_report:
          purchase_report[user] = []
        purchase_report[user].append("%s for %s" % (item_name, amount))


  for user in sorted(purchase_report.keys()):
    print "Purchases by %s" % user
    for sale in purchase_report[user]:
      print "  ", sale

def main(argv):

  if len(argv) < 1:
    xml_filename = "../data/the-packrat-early-2014-171135.xml"

  else:
    xml_filename = argv[0]

  if len(argv) < 2:
    bgg_user = "thepackrat"
  else:
    bgg_user = argv[1]

  geeklist_data = ReadAndParseXML(xml_filename)
  # BasicXMLReport(geeklist_data)

  AuctionReport(geeklist_data, bgg_user)


if __name__ == "__main__":
  main(sys.argv[1:])
